package com.github.editor;

import com.github.apk.arsc.*;
import com.github.editor.util.*;
import com.github.editor.zip.ApacheZipUtil;
import com.github.editor.zip.IZipUtil;

import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ApkEditor implements Closeable {
    private final String apkPath;
    private XmlChunk xmlChunk;
    private final String workPath;
    private ResourceTableChunk resourceTableChunk;
    private ArscBlamer arscBlamer;
    private final Map<String, String> updateList = new HashMap<>();
    private static final IZipUtil zipUtil = new ApacheZipUtil();
    private boolean debug;

    public void setDebug(boolean debug) {
        this.debug = debug;
    }

    public ApkEditor(String apkPath, String workPath) {
        this.apkPath = apkPath;
        this.workPath = workPath;
    }

    public void save(String output) throws IOException {
        if (updateList.containsKey(XmlChunk.XML_NAME)) {
            if (xmlChunk != null) {
                String new_xml = new File(workPath, XmlChunk.XML_NAME).getPath();
                try (FileOutputStream outputStream = new FileOutputStream(new_xml)) {
                    outputStream.write(xmlChunk.toByteArray());
                }
                updateList.put(XmlChunk.XML_NAME, new_xml);
            }
        }
        if (updateList.containsKey(ResourceTableChunk.RESOURCES_ARSC)) {
            String new_arsc = new File(workPath, ResourceTableChunk.RESOURCES_ARSC).getAbsolutePath();
            try (FileOutputStream outputStream = new FileOutputStream(new_arsc)) {
                outputStream.write(resourceTableChunk.toByteArray());
            }
            updateList.put(ResourceTableChunk.RESOURCES_ARSC, new_arsc);
        }
        if(debug){
            for (Map.Entry<String, String> e : updateList.entrySet()) {
                System.out.println("update " + e.getKey() + "->" + e.getValue());
            }
        }
        zipUtil.createNew(apkPath, output, updateList);
    }


    @Override
    public void close() {
        release();
    }

    public void release() {
        for (String file : updateList.values()) {
            FileUtil.delete(file);
        }
        for (AndroidIcon ai : AndroidIcon.values()) {
            FileUtil.delete(getIconPath(ai, workPath).getPath());
        }
    }

    public  static String getMetadata(String apkPath, String metaName) throws Throwable {
        XmlChunk xmlChunk;
        if(apkPath.endsWith(".xml")){
            xmlChunk = XmlChunk.parse(FileUtil.ReadAllBytes(apkPath));
        } else {
            xmlChunk = XmlChunk.parse(zipUtil.ReadZipEntryBytes(apkPath, XmlChunk.XML_NAME));
        }
        XmlStartElementChunk application = xmlChunk.getFirstStartTag("application");
        if(application == null){
            throw new IllegalArgumentException("not found application");
        }
        List<XmlStartElementChunk> metaDatas = application.getChildren("meta-data");
        for(XmlStartElementChunk metaData : metaDatas) {
            XmlAttribute name = metaData.getAttribute("{http://schemas.android.com/apk/res/android}name");
            if (name != null) {
                if (metaName.equals(name.rawValue())) {
                    XmlAttribute value = metaData.getAttribute("{http://schemas.android.com/apk/res/android}value");
                    if (value != null) {
                        ResourceValue resourceValue = value.typedValue();
                        if(resourceValue != null && resourceValue.type() == ResourceValue.Type.STRING){
                            return xmlChunk.getString(resourceValue.data());
                        }
                        return value.rawValue();
                    }
                }
            }
        }
        throw new IllegalArgumentException("not found:"+metaName);
    }

    public boolean changeAppTitle(String newAppName) {
        if (TextUtils.isEmpty(newAppName) || xmlChunk == null || resourceTableChunk == null) {
            return false;
        }
        XmlStartElementChunk application = xmlChunk.getFirstStartTag("application");
        if(application == null){
            return false;
        }
        XmlAttribute labelName = application.getAttribute("{http://schemas.android.com/apk/res/android}label");
        if (labelName == null) {
            return false;
        }
        if(labelName.rawValueIndex() >= 0){
            if (debug) {
                System.out.println("change app's label by string, old="+labelName.rawValueIndex());
            }
            //名字写死在xml
            labelName.withValue(xmlChunk.addString(newAppName));
            labelName.typedValue().withData(ResourceValue.Type.STRING, xmlChunk.addString(newAppName));
            return true;
        }
        if (labelName.typedValue().type() != ResourceValue.Type.REFERENCE) {
            if (debug) {
                System.out.println("change app's label by string2");
            }
            labelName.withValue(xmlChunk.addString(newAppName));
            labelName.typedValue().withData(ResourceValue.Type.STRING, xmlChunk.addString(newAppName));
            return true;
        }
        ArscBlamer arscBlamer = getArscBlamer();
        int count = 0;
        for (TypeChunk chunk : arscBlamer.getTypeChunks()) {
            PackageChunk packageChunk = chunk.getPackageChunk();
            if (packageChunk != null) {
                if ("string".equals(chunk.getTypeName())) {
                    for (Map.Entry<Integer, TypeChunk.Entry> item : chunk.getEntries().entrySet()) {
                        ResourceValue rv = item.getValue().value();
                        if (rv != null && rv.type() == ResourceValue.Type.STRING) {
                            if (item.getValue().getResourceId() == labelName.typedValue().data()) {
                                if (debug) {
                                    String keyName = packageChunk.getKeyStringPool().getString(item.getValue().keyIndex());
                                    String value = resourceTableChunk.getStringPool().getString(rv.data());
                                    System.out.printf("id=0x%x, name=@%s/%s, value=%s%n", labelName.typedValue().data(), chunk.getResFolderName(), keyName, value);
                                }
                                int newIndex = resourceTableChunk.getStringPool().addString(newAppName);//修改应用名
                                item.getValue().update(rv.withData(newIndex));
                                count++;
                            }
                        }
                    }
                }
            }
        }
        if (count > 0) {
            updateList.put(ResourceTableChunk.RESOURCES_ARSC, null);
        }
        return count > 0;
    }

    private ArscBlamer getArscBlamer() {
        if (arscBlamer == null) {
            arscBlamer = new ArscBlamer(resourceTableChunk);
            arscBlamer.blame();
        }
        return arscBlamer;
    }
    private File getIconPath(AndroidIcon type, String workPath){
        return new File(workPath, type.name + ".png");
    }
    public boolean changeAppIcon(String iconUrl) {
        if (TextUtils.isEmpty(iconUrl) || xmlChunk == null || resourceTableChunk == null) {
            return false;
        }
        XmlStartElementChunk application = xmlChunk.getFirstStartTag("application");
        if(application == null){
            return false;
        }
        //app_name/icon
        XmlAttribute iconName = application.getAttribute("{http://schemas.android.com/apk/res/android}icon");
        XmlAttribute iconRoundName = application.getAttribute("{http://schemas.android.com/apk/res/android}roundIcon");
        if(iconName == null && iconRoundName == null){
            if(debug){
                System.out.println("not found icon/roundIcon");
            }
            return false;
        }
        ArscBlamer arscBlamer = getArscBlamer();
        File iconFile;
        final HashMap<String, String> iconPaths = new HashMap<>();
        boolean needGenIcon = false;
        if (iconUrl.startsWith("http://") || iconUrl.startsWith("https://")) {
            //下载图片
            File file = new File(workPath, "cache/" + MD5Util.md5String(iconUrl));
            if (!file.exists() || file.length() == 0) {
                if (!HttpUtil.download(iconUrl, file.getAbsolutePath())) {
                    return false;
                }
            }
            iconFile = file;
        } else {
            iconFile = new File(iconUrl);
            String prefix = "mipmap-";
            if (iconFile.isDirectory()) {
                File[] files = iconFile.listFiles();
                if(files != null) {
                    for (File d : files) {
                        if (d.isDirectory() && d.getName().startsWith(prefix)) {
                            String type = d.getName().substring(prefix.length());
                            File icon = new File(d, "ic_launcher.png");
                            if(icon.exists()){
                                iconPaths.put(type, icon.getAbsolutePath());
                            }
                        } else if (d.isFile() && d.getName().endsWith("dpi.png")) {
                            String type;
                            if (d.getName().startsWith(prefix)) {
                                type = d.getName().substring(prefix.length());
                            } else {
                                int end = d.getName().lastIndexOf(".");
                                type = end > 0 ? d.getName().substring(0, end) : d.getName();
                            }
                            iconPaths.put(type, d.getAbsolutePath());
                        }
                    }
                    System.out.println("found icon files:" + iconPaths.size());
                }
            }
        }
        needGenIcon = iconPaths.size() == 0;
        //_res_dir + '\\mipmap-ldpi') + '\\ic_launcher.png'
        if(needGenIcon) {
            //裁剪图片
            for (AndroidIcon type : AndroidIcon.values()) {
                File icon = getIconPath(type, workPath);
                PngUtil.resize(iconFile.getAbsolutePath(), icon.getPath(), type.size, type.size);
                iconPaths.put(type.name, icon.getAbsolutePath());
            }
            System.out.println("gen icon files:" + iconPaths.size());
        }
        int count = 0;
        for (TypeChunk chunk : arscBlamer.getTypeChunks()) {
            PackageChunk packageChunk = chunk.getPackageChunk();
            if (packageChunk != null) {
                if ("drawable".equals(chunk.getTypeName()) || "mipmap".equals(chunk.getTypeName())) {
                    for (Map.Entry<Integer, TypeChunk.Entry> item : chunk.getEntries().entrySet()) {
                        ResourceValue rv = item.getValue().value();
                        if (rv != null && rv.type() == ResourceValue.Type.STRING) {
                            String value = resourceTableChunk.getStringPool().getString(rv.data());
                            if (iconName != null && item.getValue().getResourceId() == iconName.typedValue().data()) {
                                int index = chunk.getResFolderName().indexOf('-');
                                String type = index > 0 ? chunk.getResFolderName().substring(index + 1) : chunk.getResFolderName();
                                String path = iconPaths.get(type);
                                if (path != null) {
                                    count++;
                                    updateList.put(value, path);
                                }
                                if(debug){
                                    String keyName = packageChunk.getKeyStringPool().getString(item.getValue().keyIndex());
                                    System.out.printf("id=0x%x, name=@%s/%s, value=%s%n", iconName.typedValue().data(), chunk.getResFolderName(), keyName, value);
                                }
//                            } else if (iconRoundName != null && item.getValue().getResourceId() == iconRoundName.data) {
                                //TODO android:roundIcon
                                // String path = getTempIconPath(chunk.getResFolderName());
                                //if(path != null) {
                                //    updateList.put(keyName, path);
                                // }
                                // System.out.println(String.format("id=0x%x, name=@%s/%s, value=%s", iconRoundName.data, chunk.getResFolderName(), keyName, value));
                            }
                        }
                    }
                }
            }
        }
        return count > 0;
    }

    public ResourceTableChunk getResourceTableChunk() {
        return resourceTableChunk;
    }

    public boolean changeMetaData(String metaName, String newValue) {
        if (xmlChunk == null || TextUtils.isEmpty(metaName) || TextUtils.isEmpty(newValue)) {
            return false;
        }
        XmlStartElementChunk application = xmlChunk.getFirstStartTag("application");
        if(application == null){
            if(debug) {
                System.out.println("not found application");
            }
            return false;
        }
        boolean isChanged = false;
        List<XmlStartElementChunk> metaDatas = application.getChildren("meta-data");
        for(XmlStartElementChunk metaData : metaDatas) {
            XmlAttribute name = metaData.getAttribute("{http://schemas.android.com/apk/res/android}name");
            if (name != null) {
                if (metaName.equals(name.rawValue())) {
                    XmlAttribute value = metaData.getAttribute("{http://schemas.android.com/apk/res/android}value");
                    if (value != null) {
                        String old;
                        ResourceValue tv = value.typedValue();
                        if (tv != null && tv.type() == ResourceValue.Type.STRING) {
                            old = xmlChunk.getString(tv.data());
                        } else {
                            old = value.rawValue();
                        }
                        if(old != null &&old.equals(newValue)){
                            if (debug) {
                                System.out.println("dont' change metadata:" + old + "->" + newValue);
                            }
                            return true;
                        }
                        int id = xmlChunk.addString(newValue);
                        if(tv != null){
                            value.withResValue(tv.withData(ResourceValue.Type.STRING, id));
                        }
                        if(value.rawValue() != null) {
                            value.withValue(id);
                        }
                        if (debug) {
                            System.out.println("change metadata:" + old + "->" + newValue);
                        }
                        isChanged = true;
                        break;
                    }
                }
            }
        }
        if(!isChanged){
            if (debug) {
                System.out.println("need add metadata:" + metaName + "=" + newValue);
            }
//            XmlStartElementChunk metaData = new XmlStartElementChunk();
            //TODO
//            XmlAttribute name = new XmlAttribute(xmlChunk.addString("http://schemas.android.com/apk/res/android"), xmlChunk.addString("name"),
//                    xmlChunk.addString(metaName), new ResourceValue(), metaData);
        }
        updateList.put(XmlChunk.XML_NAME, null);
        return true;
    }

    public boolean parse() {
        if (!new File(apkPath).exists()) {
            if(debug) {
                System.err.println(apkPath + " is not exist");
            }
            return false;
        }
        try {
            zipUtil.exportFiles(apkPath, workPath, XmlChunk.XML_NAME, ResourceTableChunk.RESOURCES_ARSC);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        XmlChunk xmlChunk;
        try {
            xmlChunk = XmlChunk.parse(zipUtil.ReadZipEntryBytes(apkPath, XmlChunk.XML_NAME));
        } catch (Throwable e) {
            e.printStackTrace();
            return false;
        }
        if (xmlChunk != null) {
            this.xmlChunk = xmlChunk;
            try {
                resourceTableChunk = ResourceTableChunk.parse(zipUtil.ReadZipEntryBytes(apkPath, ResourceTableChunk.RESOURCES_ARSC));
            } catch (Throwable e) {
                e.printStackTrace();
                return false;
            }
            if(debug) {
                System.out.println("parse xml and arsc is success");
            }
            return true;
        }
        return false;
    }
}
